跳到主要内容

Unity 单例泛型模板

单例模板的作用

我们在使用 Unity 开发开发游戏的时候,很多游戏管理类都是唯一的,比如 GameManager、UIManager 脚本类就是唯一的,由一个 GameObject 游戏体运行就可以了,这个时候 GameManager、UIManager 就是一个单例类,下面写一个泛型单例模板,只要哪个类需要用单例类,只需要继承这个类就可以了。

单例模板

它的原理看注释

/// <summary>
/// 单例模板
/// </summary>
/// <typeparam name="T">泛型类型得继承自 MonoBehaviour</typeparam>
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance;
private static readonly object _lock = new object();

/// <summary>
/// 是否需要丢进 DontDestroyOnLoad 里面
/// </summary>
public bool isDontDestroyOnLoad = false;

/// <summary>
/// 实例的属性
/// </summary>
public static T instance
{
get {
//if (_applicationIsQuitting) {
// return null;
//}

lock (_lock) {
if (_instance != null) return _instance;

// FindObjectOfType 返回找到的第一个对象
// 不过这个函数是非常慢的(下同)。不推荐在每帧使用这个函数,大多数情况下你可以使用单例模式代替。
_instance = (T)FindObjectOfType (typeof(T));

// FindObjectsOfType(typeof(Type)) 返回 Type 类型的所有激活的加载的物体列表(就是返回一个已经实例的对象)
// 如果找到多个单例对象则抛出警告
if (FindObjectsOfType (typeof(T)).Length > 1) {
if (Application.isEditor)
Debug.LogWarning(
"MonoSingleton<T>.Instance: Only 1 singleton instance can exist in the scene. Null will be returned.");

return _instance;
}
if (_instance != null) return _instance;



// 如果没有找到则表示不存在这个对象,则需要创建
// 自动创建一个名为 “(singleton)类名” 的游戏体,一旦创建,在切换场景的时候该类也不会销毁,所以多个场景只能有一份该类。
var singleton = new GameObject (); // 找不到才需要自己创建单例

_instance = singleton.AddComponent<T> ();
singleton.name = "(singleton) " + typeof(T).ToString ();

return _instance;
}
}
}

private static bool _applicationIsQuitting = false;

public void Awake()
{
if (isDontDestroyOnLoad)
{
// 让该实例切换场景时不被销毁
DontDestroyOnLoad (instance);
}
}

/// <summary>
/// Unity 会在销毁对象时调用这个方法,所以需要将这个对象存到 DontDestroyOnLoad
/// </summary>
public void OnDestroy()
{
_applicationIsQuitting = true;
}
}

这个类功能强大,实现了多线程锁机制(一般情况用不上这个锁),能够保护数据安全,如果场景中没有该类的话,会自动创建一个名为 “(singleton)类名” 的游戏体,一旦创建,在切换场景的时候该类也不会销毁,所以多个场景只能有一份该类。

使用时只需继承这个类就行了

// 这个泛型填自己
public class UIManager : Singleton<UIManager> {
...
}

where 关键字

参考资料 where(泛型类型约束)(C# 参考)

泛型定义中的 where 子句指定对用作泛型类型、方法、委托或本地函数中类型参数的参数类型的约束。 约束可指定接口、基类或要求泛型类型为引用、值或非托管类型。 它们声明类型参数必须具备的功能。

例如,可以声明一个泛型类 AGenericClass,以使类型参数 T 实现 IComparable<T> 接口:

public class AGenericClass<T> where T : IComparable<T> { }

说白了等价于 Java 中的 上界通配符

// 上界通配符()
List<? extends A> // 可以包含 A及其子类

重复创建单例的问题

如果重复进入有单例对象的那个场景会发现出现了多个单例

可以把自己销毁

private void Awake()
{
if (_instance != null)
{
Destroy(gameObject);
return;
}

_instance = this;
orbs = new List<Orb>();
DontDestroyOnLoad(this);
}

方法二:

创建一个初始化的场景,在初始化场景里面的某个游戏对象的全局脚本中,把所有游戏对象全部设置成 DontDestroyOnLoad,也就是切换场景时不销毁,接着进入到第一个游戏场景,也就是说逻辑永远不会再返回初始化场景,也就不会存在来回切场景 DontDestroyOnLoad 没有删除的问题

方法三:

public class Test: MonoBehaviour {

public GameObject prefab; // 这是个预制,直接拖拽赋值
GameObject clone; // 用来接收预制的克隆体
static bool isHaveClone = false; // 静态变量,所有脚本共用,也就是保证预制只能被克隆一次,不会出现多个角色

// Use this for initialization
void Start () {
if (!isHaveClone)
{
clone = (GameObject)GameObject.Instantiate(prefab);
isHaveClone = true;
GameObject.DontDestroyOnLoad(clone);
}
}
}